import { useState, useCallback, useEffect, memo } from "react"

// 使用React.memo包装的子组件
const Button = memo(
  ({
    onClick,
    children,
  }: {
    onClick: () => void
    children: React.ReactNode
  }) => {
    console.log(`Button ${children} 重新渲染`)
    return <button onClick={onClick}>{children}</button>
  }
)

const ExpensiveComponent = memo(({ value }: { value: number }) => {
  console.log("ExpensiveComponent 重新渲染", value)

  // 模拟耗时计算
  const startTime = performance.now()
  while (performance.now() - startTime < 50) {
    // 空循环，模拟复杂计算
  }

  return <div>复杂组件计算结果: {value}</div>
})

// 主组件
const UseCallbackDemo = () => {
  const [count, setCount] = useState(0)
  const [text, setText] = useState("")
  const [renderCount, setRenderCount] = useState(0)

  // 不使用 useCallback 的函数
  const incrementWithoutCallback = () => {
    setCount(count + 1)
  }

  // 使用 useCallback 的函数
  const incrementWithCallback = useCallback(() => {
    setCount((prevCount) => prevCount + 1)
  }, [])

  // 依赖于 count 的回调
  const incrementByValue = useCallback((value: number) => {
    setCount((prevCount) => prevCount + value)
  }, [])

  // 错误的依赖：使用外部值但没有添加依赖
  const brokenCallback = useCallback(() => {
    console.log(`当前计数: ${count}`) // 闭包陷阱：count不会更新
    setCount(count + 1) // 这里的count会是创建函数时的值
  }, []) // 应该添加 [count] 作为依赖

  // 追踪组件渲染次数
  useEffect(() => {
    setRenderCount((prev) => prev + 1)
  }, [count, text])

  return (
    <div className="demo-section">
      <h2>useCallback Hook 面试题讲解</h2>

      <div className="card">
        <h3>1. 什么是 useCallback?</h3>
        <p>
          useCallback 是 React 的一个
          Hook，用于缓存回调函数实例，避免在每次渲染时创建新的函数引用。
          这在将回调传递给依赖引用相等性优化的子组件时特别有用。
        </p>

        <div className="code-example">
          <code>
            const memoizedCallback = useCallback( () =&gt; {"{"}
            doSomething(a, b);
            {"}"}, [a, b] );
          </code>
        </div>
      </div>

      <div className="card">
        <h3>2. 常见面试问题: useCallback 解决了什么问题?</h3>
        <p>
          useCallback
          主要解决的问题是避免函数在每次渲染时重新创建，从而导致接收这些函数作为
          props 的子组件不必要的重新渲染。 它通过记忆函数引用来优化性能。
        </p>
        <div className="example-output">
          <p>
            当前计数: <strong>{count}</strong>
          </p>
          <p>
            组件已渲染 <strong>{renderCount}</strong> 次
          </p>

          <div className="flex-container">
            <div className="flex-item">
              <p>输入文本 (会触发重新渲染):</p>
              <input
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="输入文本..."
              />
              <p>
                当前输入: <strong>{text || "(空)"}</strong>
              </p>
            </div>
          </div>
        </div>
      </div>

      <div className="card">
        <h3>3. 使用场景: 优化子组件重新渲染</h3>
        <p>
          当子组件使用 React.memo 或 shouldComponentUpdate 优化时，使用
          useCallback 可以确保传递给它们的函数引用不会改变：
        </p>

        <div className="example-output">
          <p>
            注意：每次输入文本时，组件都会重新渲染。查看控制台以观察哪些按钮组件重新渲染了。
          </p>

          <div>
            <p>没有使用 useCallback (每次渲染都会创建新函数):</p>
            <Button onClick={incrementWithoutCallback}>
              不使用 useCallback 的按钮
            </Button>
          </div>

          <div>
            <p>使用了 useCallback (函数引用保持不变):</p>
            <Button onClick={incrementWithCallback}>
              使用 useCallback 的按钮
            </Button>
          </div>
        </div>

        <div className="code-example">
          <code>
            // 不使用 useCallback - 每次渲染都会创建新函数 const
            incrementWithoutCallback = () =&gt; {"{"}
            setCount(count + 1);
            {"}"}; // 使用 useCallback - 函数引用保持不变 const
            incrementWithCallback = useCallback(() =&gt; {"{"}
            setCount(prevCount =&gt; prevCount + 1);
            {"}"}, []);
          </code>
        </div>
      </div>

      <div className="card">
        <h3>4. 常见面试问题: useCallback 的依赖项数组</h3>
        <p>
          与 useEffect 类似，useCallback
          接受一个依赖项数组作为第二个参数。只有当依赖项发生变化时，才会返回新的函数引用。
        </p>

        <div className="warning">
          <p>
            <strong>常见错误:</strong>{" "}
            使用外部值但没有添加到依赖项数组中，导致闭包陷阱。
          </p>
        </div>

        <div>
          <p>错误示例 (闭包陷阱):</p>
          <Button onClick={brokenCallback}>
            闭包陷阱示例 (点击后观察控制台)
          </Button>
        </div>

        <div className="code-example">
          <p>错误的实现 (闭包陷阱):</p>
          <code>
            const brokenCallback = useCallback(() =&gt; {"{"}
            console.log(`当前计数: ${count}`); // 会一直显示初始值
            setCount(count + 1); // 这里的count会是创建函数时的值
            {"}"}, []); // 缺少依赖项 [count]
          </code>

          <p>正确的实现:</p>
          <code>
            // 1. 添加依赖项 const correctCallback = useCallback(() =&gt; {"{"}
            console.log(`当前计数: ${count}`); setCount(count + 1);
            {"}"}, [count]); // 正确添加依赖 // 2. 使用函数式更新避免依赖 const
            betterCallback = useCallback(() =&gt; {"{"}
            setCount(prevCount =&gt; prevCount + 1); // 不需要依赖count
            {"}"}, []);
          </code>
        </div>
      </div>

      <div className="card">
        <h3>5. 常见面试问题: useCallback vs useMemo</h3>
        <p>useCallback 和 useMemo 都用于性能优化，但用途略有不同:</p>

        <table>
          <thead>
            <tr>
              <th>Hook</th>
              <th>用途</th>
              <th>返回值</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <strong>useCallback</strong>
              </td>
              <td>缓存函数引用</td>
              <td>被记忆的回调函数</td>
            </tr>
            <tr>
              <td>
                <strong>useMemo</strong>
              </td>
              <td>缓存计算结果</td>
              <td>被记忆的值</td>
            </tr>
          </tbody>
        </table>

        <div className="code-example">
          <p>对比:</p>
          <code>
            // useCallback: 返回记忆的函数 const memoizedCallback =
            useCallback(() =&gt; {"{"}
            doSomething(a, b);
            {"}"}, [a, b]); // useMemo: 返回记忆的值 const memoizedValue =
            useMemo(() =&gt; {"{"}
            return computeExpensiveValue(a, b);
            {"}"}, [a, b]); // useMemo可以模拟useCallback const
            simulatedCallback = useMemo(() =&gt; {"{"}
            return () =&gt; doSomething(a, b);
            {"}"}, [a, b]);
          </code>
        </div>
      </div>

      <div className="card">
        <h3>6. 实际应用场景</h3>
        <div className="example-output">
          <p>以下是一个模拟耗时计算的组件，仅在点击专用按钮时更新:</p>
          <ExpensiveComponent value={count} />
          <Button onClick={() => incrementByValue(5)}>
            增加5 (使用缓存回调)
          </Button>
        </div>
      </div>

      <div className="card">
        <h3>7. useCallback 面试核心要点</h3>
        <ul>
          <li>useCallback 用于缓存函数实例，避免子组件不必要的重新渲染</li>
          <li>仅当依赖项变化时，useCallback 才会返回新的函数引用</li>
          <li>对于传递给使用了 React.memo 的子组件的回调函数特别有用</li>
          <li>小心闭包陷阱：确保依赖数组包含回调中使用的所有外部变量</li>
          <li>
            使用函数式更新可以减少依赖项，例如 setCount(prev =&gt; prev + 1)
          </li>
          <li>不要过度使用 useCallback，只有当性能优化确实需要时才使用</li>
        </ul>
      </div>
    </div>
  )
}

export default UseCallbackDemo
